import json

from decimal import Decimal

from django.shortcuts import render
from django.views import View
from django.http import JsonResponse
from django.utils import timezone
from django.db import transaction
from django_redis import get_redis_connection

from meiduo_mall.utils.views import LoginRequiredJSONMixin
from goods.models import SKU
from users.models import Address
from .models import OrderInfo, OrderGoods
from meiduo_mall.utils.logger import logger


class OrderSettlementView(LoginRequiredJSONMixin, View):
    """订单结算接口"""

    def get(self, request):

        # 1. 提取参数
        user = request.user

        # 2. 校验参数

        # 3. 业务处理
        # 3.1 获取当前登录用户关联的收货地址
        addresses = user.addresses.filter(is_deleted=False)

        # 构建字典响应数据
        address_list = []
        for address in addresses:
            address_list.append({
                'id': address.id,
                'province': address.province.name,
                'city': address.city.name,
                'district': address.district.name,
                'place': address.place,
                'mobile': address.mobile,
                'receiver': address.receiver,
            })

        # 3.2 获取当前登录用户购物车选中的商品
        conn = get_redis_connection('carts')
        redis_carts = conn.hgetall(f'carts_{user.id}')  # 提取购物车商品数量
        redis_selected = conn.smembers(f'selected_{user.id}')  # 选中状态

        sku_list = []  # 构建Sku商品信息返回数据
        sku_ids = redis_carts.keys()
        for sku_id in sku_ids:
            if sku_id in redis_selected:  # 只有被选中的sku才处理
                sku = SKU.objects.get(pk=sku_id)
                sku_list.append({
                    'id': sku.id,
                    'name': sku.name,
                    'default_image_url': sku.default_image.url,
                    'count': int(redis_carts[sku_id]),
                    'price': sku.price,
                })

        # 运费
        # freight = 10.0  # float类型（十进制浮点数）--> 可能在后续计算过程中丢失精度
        freight = Decimal('10.0')  # Decimal --> 该类型可以保证计算过程中的精度

        # 4. 构建响应
        return JsonResponse({'code': 0,
                             'errmsg': 'ok',
                             'context': {
                                 'addresses': address_list,
                                 'skus': sku_list,
                                 'freight': freight,
                             }
                             }, status=200, json_dumps_params={'ensure_ascii': False})


class OrderCommitView(View):
    """提交订单"""

    def post(self, request):
        """保存订单信息和订单商品信息"""

        data = json.loads(request.body.decode())
        address_id = data.get('address_id')
        pay_method = data.get('pay_method')

        if not all([address_id, pay_method]):
            return JsonResponse({'code': 400, 'errmsg': '缺少参数'}, status=400, json_dumps_params={'ensure_ascii': False})

        try:
            address = Address.objects.get(pk=address_id)
        except Exception as e:
            return JsonResponse({'code': 400, 'errmsg': '地址不存在'}, status=400, json_dumps_params={'ensure_ascii': False})

        if pay_method not in OrderInfo.PAY_METHODS_ENUM.values():
            return JsonResponse({'code': 400, 'errmsg': '不支持该支付方式'}, status=400, json_dumps_params={'ensure_ascii': False})

        # 业务数据处理
        # 1. 新建订单 ——>  OrderInfo
        user = request.user

        cur_time = timezone.localtime()

        # order_id = '20201224111256' + '00000001'
        order_id = cur_time.strftime('%Y%m%d%H%M%S') + "%08d" % user.id

        with transaction.atomic():  # TODO: 创建事务
            # 1. 设置一个保存点 ——> 用于回滚
            save_id = transaction.savepoint()

            try:
                order = OrderInfo.objects.create(
                    order_id=order_id,
                    user=user,
                    address=address,
                    total_count=0,  # 初始化为0，后续统计订单商品的时候修改
                    total_amount=Decimal('0'),
                    freight=Decimal('10.0'),
                    pay_method=pay_method,
                    # 如果选择支付宝，状态为：未支付，如果用户选择货到付款，状态为：为发货
                    status=OrderInfo.ORDER_STATUS_ENUM['UNPAID'] if pay_method == OrderInfo.PAY_METHODS_ENUM['ALIPAY'] else OrderInfo.ORDER_STATUS_ENUM['UNSEND'],
                )

                # 2. 新建订单商品 ——> OrderGoods
                # 约定购物车字典格式数据
                cart_dict = {}
                # 获取redis购物车数据
                conn = get_redis_connection('carts')
                redis_carts = conn.hgetall(f'carts_{user.id}')
                redis_selected = conn.smembers(f'selected_{user.id}')

                sku_ids = redis_carts.keys()
                for sku_id in sku_ids:
                    if sku_id in redis_selected:
                        cart_dict[int(sku_id)] = {
                            'count': int(redis_carts[sku_id]),
                            'selected': sku_id in redis_selected,
                        }

                sku_selected_ids = cart_dict.keys()
                for sku_id in sku_selected_ids:
                    # sku_id是每一个被选中的sku的id

                    count = cart_dict[sku_id]['count']  # 用户购买量

                    while True:  # TODO： 乐观锁
                        # 读取旧库存和销量数据数据
                        sku = SKU.objects.get(pk=sku_id)
                        old_stock = sku.stock  # 旧库存
                        old_sales = sku.sales  # 旧销量

                        if count > old_stock:
                            # 库存不够，下单失败
                            # 2. 回滚事务，到新建订单order之前的保存点
                            transaction.savepoint_rollback(save_id)

                            return JsonResponse({'code': 400, 'errmsg': f'{sku.name}-{sku.id}-商品库存不足'}, status=200,
                                                json_dumps_params={'ensure_ascii': False})

                        # 计算新库存和销量
                        new_stock = old_stock - count
                        new_sales = old_sales + count

                        # 基于旧数据查找，并更新
                        # update返回值为一个整数，表示受到影响的数据的条数
                        result = SKU.objects.filter(pk=sku_id, stock=old_stock, sales=old_sales).update(
                            stock=new_stock,
                            sales=new_sales
                        )
                        if result == 0:
                            # 如果Result为0，说明没有数据被更新，说明filter没有过滤出数据，
                            # 说明旧数据发生改变，说明有别的事务介入, 返回重新开始计算
                            continue

                        # result不为0， 表示基于旧数据查找并成功更新库存和销量，针对此次sku的更新完成，跳出循环
                        spu = sku.spu  # 更新spu销量
                        spu.sales += count
                        spu.save()
                        break

                    # 新建OrderGoods对象保存数据库
                    OrderGoods.objects.create(
                        order=order,
                        sku=sku,
                        count=count,
                        price=sku.price,
                    )

                    # 更新订单的商品数量和总价格
                    order.total_count += count
                    order.total_amount += (sku.price * count)

                order.total_amount += order.freight  # 把运费累加到总价中
                order.save()
            except Exception as e:
                # 回滚事务，到新建订单order之前的保存点
                transaction.savepoint_rollback(save_id)
                logger('下单失败：', e)
                return JsonResponse({'code': 400, 'errmsg': f'下单失败'}, status=200,
                                    json_dumps_params={'ensure_ascii': False})

            # 3. 提交事务，删除保存点
            transaction.savepoint_commit(save_id)

        # TODO: 清除已下单的购物车商品
        conn.hdel(f'carts_{user.id}', *sku_selected_ids)
        conn.srem(f'selected_{user.id}', *sku_selected_ids)

        return JsonResponse({'code': 0, 'errmsg': 'ok', 'order_id': order_id}, status=200, json_dumps_params={'ensure_ascii': False})




